package com.hqd.ch03.v51.factory.support;

import com.hqd.ch03.v51.beans.factory.config.BeanDefinition;
import com.hqd.ch03.v51.beans.factory.support.AbstractBeanDefinition;
import com.hqd.ch03.v51.config.ConstructorArgumentValues;
import com.hqd.ch03.v51.config.MutablePropertyValues;
import com.hqd.ch03.v51.core.ResolvableType;
import com.hqd.ch03.v51.factory.config.BeanDefinitionHolder;

import java.lang.reflect.*;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Supplier;

/**
 * 可以作为其他 Bean 定义的父类，但不能作为子类，即不能设置父 Bean
 */
public class RootBeanDefinition extends AbstractBeanDefinition {
    public final Object constructorArgumentLock = new Object();
    public final Object postProcessingLock = new Object();
    public volatile boolean stale;
    public volatile ResolvableType targetType;
    public boolean allowCaching = true;
    public boolean isFactoryMethodUnique;
    /**
     * Package-visible field for caching the determined Class of a given bean definition.
     */

    public volatile Class<?> resolvedTargetType;

    /**
     * Package-visible field for caching if the bean is a factory bean.
     */

    public volatile Boolean isFactoryBean;

    /**
     * Package-visible field for caching the return type of a generically typed factory method.
     */

    public volatile ResolvableType factoryMethodReturnType;

    /**
     * Package-visible field for caching a unique factory method candidate for introspection.
     */

    public volatile Method factoryMethodToIntrospect;

    /**
     * Package-visible field for caching a resolved destroy method name (also for inferred).
     */

    public volatile String resolvedDestroyMethodName;
    /**
     * Package-visible field for caching the resolved constructor or factory method.
     */

    public Executable resolvedConstructorOrFactoryMethod;
    /**
     * Package-visible field that marks the constructor arguments as resolved.
     */
    public boolean constructorArgumentsResolved = false;
    /**
     * Package-visible field for caching fully resolved constructor arguments.
     */

    public Object[] resolvedConstructorArguments;
    /**
     * Package-visible field for caching partly prepared constructor arguments.
     */

    public Object[] preparedConstructorArguments;
    /**
     * Package-visible field that indicates MergedBeanDefinitionPostProcessor having been applied.
     */
    public boolean postProcessed = false;
    /**
     * Package-visible field that indicates a before-instantiation post-processor having kicked in.
     */

    public volatile Boolean beforeInstantiationResolved;

    private BeanDefinitionHolder decoratedDefinition;

    private AnnotatedElement qualifiedElement;

    private Set<Member> externallyManagedConfigMembers;


    private Set<String> externallyManagedInitMethods;


    private Set<String> externallyManagedDestroyMethods;


    /**
     * Create a new RootBeanDefinition, to be configured through its bean
     * properties and configuration methods.
     *
     * @see #setBeanClass
     * @see #setScope
     * @see #setConstructorArgumentValues
     * @see #setPropertyValues
     */
    public RootBeanDefinition() {
        super();
    }

    /**
     * Create a new RootBeanDefinition for a singleton.
     *
     * @param beanClass the class of the bean to instantiate
     * @see #setBeanClass
     */
    public RootBeanDefinition(Class<?> beanClass) {
        super();
        setBeanClass(beanClass);
    }

    /**
     * Create a new RootBeanDefinition for a singleton bean, constructing each instance
     * through calling the given supplier (possibly a lambda or method reference).
     *
     * @param beanClass        the class of the bean to instantiate
     * @param instanceSupplier the supplier to construct a bean instance,
     *                         as an alternative to a declaratively specified factory method
     * @see #setInstanceSupplier
     * @since 5.0
     */
    public <T> RootBeanDefinition(Class<T> beanClass, Supplier<T> instanceSupplier) {
        super();
        setBeanClass(beanClass);
        setInstanceSupplier(instanceSupplier);
    }

    /**
     * Create a new RootBeanDefinition for a scoped bean, constructing each instance
     * through calling the given supplier (possibly a lambda or method reference).
     *
     * @param beanClass        the class of the bean to instantiate
     * @param scope            the name of the corresponding scope
     * @param instanceSupplier the supplier to construct a bean instance,
     *                         as an alternative to a declaratively specified factory method
     * @see #setInstanceSupplier
     * @since 5.0
     */
    public <T> RootBeanDefinition(Class<T> beanClass, String scope, Supplier<T> instanceSupplier) {
        super();
        setBeanClass(beanClass);
        setScope(scope);
        setInstanceSupplier(instanceSupplier);
    }

    /**
     * Create a new RootBeanDefinition for a singleton,
     * using the given autowire mode.
     *
     * @param beanClass       the class of the bean to instantiate
     * @param autowireMode    by name or type, using the constants in this interface
     * @param dependencyCheck whether to perform a dependency check for objects
     *                        (not applicable to autowiring a constructor, thus ignored there)
     */
    public RootBeanDefinition(Class<?> beanClass, int autowireMode, boolean dependencyCheck) {
        super();
        setBeanClass(beanClass);
        setAutowireMode(autowireMode);
        if (dependencyCheck && getResolvedAutowireMode() != AUTOWIRE_CONSTRUCTOR) {
            setDependencyCheck(DEPENDENCY_CHECK_OBJECTS);
        }
    }

    /**
     * Create a new RootBeanDefinition for a singleton,
     * providing constructor arguments and property values.
     *
     * @param beanClass the class of the bean to instantiate
     * @param cargs     the constructor argument values to apply
     * @param pvs       the property values to apply
     */
    public RootBeanDefinition(Class<?> beanClass, ConstructorArgumentValues cargs,
                              MutablePropertyValues pvs) {

        super(cargs, pvs);
        setBeanClass(beanClass);
    }

    /**
     * Create a new RootBeanDefinition for a singleton,
     * providing constructor arguments and property values.
     * <p>Takes a bean class name to avoid eager loading of the bean class.
     *
     * @param beanClassName the name of the class to instantiate
     */
    public RootBeanDefinition(String beanClassName) {
        setBeanClassName(beanClassName);
    }

    /**
     * Create a new RootBeanDefinition for a singleton,
     * providing constructor arguments and property values.
     * <p>Takes a bean class name to avoid eager loading of the bean class.
     *
     * @param beanClassName the name of the class to instantiate
     * @param cargs         the constructor argument values to apply
     * @param pvs           the property values to apply
     */
    public RootBeanDefinition(String beanClassName, ConstructorArgumentValues cargs, MutablePropertyValues pvs) {
        super(cargs, pvs);
        setBeanClassName(beanClassName);
    }

    /**
     * Create a new RootBeanDefinition as deep copy of the given
     * bean definition.
     *
     * @param original the original bean definition to copy from
     */
    public RootBeanDefinition(RootBeanDefinition original) {
        super(original);
        this.decoratedDefinition = original.decoratedDefinition;
        this.qualifiedElement = original.qualifiedElement;
        this.allowCaching = original.allowCaching;
        this.isFactoryMethodUnique = original.isFactoryMethodUnique;
        this.targetType = original.targetType;
        this.factoryMethodToIntrospect = original.factoryMethodToIntrospect;
    }

    /**
     * Create a new RootBeanDefinition as deep copy of the given
     * bean definition.
     *
     * @param original the original bean definition to copy from
     */
    public RootBeanDefinition(BeanDefinition original) {
        super(original);
    }


    @Override
    public String getParentName() {
        return null;
    }

    @Override
    public void setParentName(String parentName) {
        if (parentName != null) {
            throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference");
        }
    }

    /**
     * Return the target definition that is being decorated by this bean definition, if any.
     */

    public BeanDefinitionHolder getDecoratedDefinition() {
        return this.decoratedDefinition;
    }

    /**
     * Register a target definition that is being decorated by this bean definition.
     */
    public void setDecoratedDefinition(BeanDefinitionHolder decoratedDefinition) {
        this.decoratedDefinition = decoratedDefinition;
    }

    /**
     * Return the {@link AnnotatedElement} defining qualifiers, if any.
     * Otherwise, the factory method and target class will be checked.
     *
     * @since 4.3.3
     */

    public AnnotatedElement getQualifiedElement() {
        return this.qualifiedElement;
    }

    /**
     * Specify the {@link AnnotatedElement} defining qualifiers,
     * to be used instead of the target class or factory method.
     *
     * @see #setTargetType(ResolvableType)
     * @see #getResolvedFactoryMethod()
     * @since 4.3.3
     */
    public void setQualifiedElement(AnnotatedElement qualifiedElement) {
        this.qualifiedElement = qualifiedElement;
    }

    /**
     * Return the target type of this bean definition, if known
     * (either specified in advance or resolved on first instantiation).
     *
     * @since 3.2.2
     */

    public Class<?> getTargetType() {
        if (this.resolvedTargetType != null) {
            return this.resolvedTargetType;
        }
        ResolvableType targetType = this.targetType;
        return (targetType != null ? targetType.resolve() : null);
    }

    /**
     * Specify a generics-containing target type of this bean definition, if known in advance.
     *
     * @since 4.3.3
     */
    public void setTargetType(ResolvableType targetType) {
        this.targetType = targetType;
    }

    /**
     * Specify the target type of this bean definition, if known in advance.
     *
     * @since 3.2.2
     */
    public void setTargetType(Class<?> targetType) {
        this.targetType = (targetType != null ? ResolvableType.forClass(targetType) : null);
    }

    /**
     * Return a {@link ResolvableType} for this bean definition,
     * either from runtime-cached type information or from configuration-time
     * {@link #setTargetType(ResolvableType)} or {@link #setBeanClass(Class)},
     * also considering resolved factory method definitions.
     *
     * @see #setTargetType(ResolvableType)
     * @see #setBeanClass(Class)
     * @see #setResolvedFactoryMethod(Method)
     * @since 5.1
     */
    @Override
    public ResolvableType getResolvableType() {
        ResolvableType targetType = this.targetType;
        if (targetType != null) {
            return targetType;
        }
        ResolvableType returnType = this.factoryMethodReturnType;
        if (returnType != null) {
            return returnType;
        }
        Method factoryMethod = this.factoryMethodToIntrospect;
        if (factoryMethod != null) {
            return ResolvableType.forMethodReturnType(factoryMethod);
        }
        return super.getResolvableType();
    }

    /**
     * Determine preferred constructors to use for default construction, if any.
     * Constructor arguments will be autowired if necessary.
     *
     * @return one or more preferred constructors, or {@code null} if none
     * (in which case the regular no-arg default constructor will be called)
     * @since 5.1
     */

    public Constructor<?>[] getPreferredConstructors() {
        return null;
    }

    /**
     * Specify a factory method name that refers to a non-overloaded method.
     */
    public void setUniqueFactoryMethodName(String name) {
        setFactoryMethodName(name);
        this.isFactoryMethodUnique = true;
    }

    /**
     * Specify a factory method name that refers to an overloaded method.
     *
     * @since 5.2
     */
    public void setNonUniqueFactoryMethodName(String name) {
        setFactoryMethodName(name);
        this.isFactoryMethodUnique = false;
    }

    /**
     * Check whether the given candidate qualifies as a factory method.
     */
    public boolean isFactoryMethod(Method candidate) {
        return candidate.getName().equals(getFactoryMethodName());
    }

    /**
     * Return the resolved factory method as a Java Method object, if available.
     *
     * @return the factory method, or {@code null} if not found or not resolved yet
     */

    public Method getResolvedFactoryMethod() {
        return this.factoryMethodToIntrospect;
    }

    /**
     * Set a resolved Java Method for the factory method on this bean definition.
     *
     * @param method the resolved factory method, or {@code null} to reset it
     * @since 5.2
     */
    public void setResolvedFactoryMethod(Method method) {
        this.factoryMethodToIntrospect = method;
    }

    /**
     * Register an externally managed configuration method or field.
     */
    public void registerExternallyManagedConfigMember(Member configMember) {
        synchronized (this.postProcessingLock) {
            if (this.externallyManagedConfigMembers == null) {
                this.externallyManagedConfigMembers = new LinkedHashSet<>(1);
            }
            this.externallyManagedConfigMembers.add(configMember);
        }
    }

    /**
     * Determine if the given method or field is an externally managed configuration member.
     */
    public boolean isExternallyManagedConfigMember(Member configMember) {
        synchronized (this.postProcessingLock) {
            return (this.externallyManagedConfigMembers != null &&
                    this.externallyManagedConfigMembers.contains(configMember));
        }
    }

    /**
     * Get all externally managed configuration methods and fields (as an immutable Set).
     *
     * @since 5.3.11
     */
    public Set<Member> getExternallyManagedConfigMembers() {
        synchronized (this.postProcessingLock) {
            return (this.externallyManagedConfigMembers != null ?
                    Collections.unmodifiableSet(new LinkedHashSet<>(this.externallyManagedConfigMembers)) :
                    Collections.emptySet());
        }
    }

    public void registerExternallyManagedInitMethod(String initMethod) {
        synchronized (this.postProcessingLock) {
            if (this.externallyManagedInitMethods == null) {
                this.externallyManagedInitMethods = new LinkedHashSet<>(1);
            }
            this.externallyManagedInitMethods.add(initMethod);
        }
    }

    /**
     * Determine if the given method name indicates an externally managed
     * initialization method.
     * <p>See {@link #registerExternallyManagedInitMethod} for details
     * regarding the format for the supplied {@code initMethod}.
     */
    public boolean isExternallyManagedInitMethod(String initMethod) {
        synchronized (this.postProcessingLock) {
            return (this.externallyManagedInitMethods != null &&
                    this.externallyManagedInitMethods.contains(initMethod));
        }
    }

    /**
     * Determine if the given method name indicates an externally managed
     * initialization method, regardless of method visibility.
     * <p>In contrast to {@link #isExternallyManagedInitMethod(String)}, this
     * method also returns {@code true} if there is a {@code private} externally
     * managed initialization method that has been
     * {@linkplain #registerExternallyManagedInitMethod(String) registered}
     * using a qualified method name instead of a simple method name.
     *
     * @since 5.3.17
     */
    public boolean hasAnyExternallyManagedInitMethod(String initMethod) {
        synchronized (this.postProcessingLock) {
            if (isExternallyManagedInitMethod(initMethod)) {
                return true;
            }
            if (this.externallyManagedInitMethods != null) {
                for (String candidate : this.externallyManagedInitMethods) {
                    int indexOfDot = candidate.lastIndexOf('.');
                    if (indexOfDot >= 0) {
                        String methodName = candidate.substring(indexOfDot + 1);
                        if (methodName.equals(initMethod)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
    }

    /**
     * Return all externally managed initialization methods (as an immutable Set).
     * <p>See {@link #registerExternallyManagedInitMethod} for details
     * regarding the format for the initialization methods in the returned set.
     *
     * @since 5.3.11
     */
    public Set<String> getExternallyManagedInitMethods() {
        synchronized (this.postProcessingLock) {
            return (this.externallyManagedInitMethods != null ?
                    Collections.unmodifiableSet(new LinkedHashSet<>(this.externallyManagedInitMethods)) :
                    Collections.emptySet());
        }
    }

    public void registerExternallyManagedDestroyMethod(String destroyMethod) {
        synchronized (this.postProcessingLock) {
            if (this.externallyManagedDestroyMethods == null) {
                this.externallyManagedDestroyMethods = new LinkedHashSet<>(1);
            }
            this.externallyManagedDestroyMethods.add(destroyMethod);
        }
    }

    /**
     * Determine if the given method name indicates an externally managed
     * destruction method.
     * <p>See {@link #registerExternallyManagedDestroyMethod} for details
     * regarding the format for the supplied {@code destroyMethod}.
     */
    public boolean isExternallyManagedDestroyMethod(String destroyMethod) {
        synchronized (this.postProcessingLock) {
            return (this.externallyManagedDestroyMethods != null &&
                    this.externallyManagedDestroyMethods.contains(destroyMethod));
        }
    }

    /**
     * Determine if the given method name indicates an externally managed
     * destruction method, regardless of method visibility.
     * <p>In contrast to {@link #isExternallyManagedDestroyMethod(String)}, this
     * method also returns {@code true} if there is a {@code private} externally
     * managed destruction method that has been
     * {@linkplain #registerExternallyManagedDestroyMethod(String) registered}
     * using a qualified method name instead of a simple method name.
     *
     * @since 5.3.17
     */
    public boolean hasAnyExternallyManagedDestroyMethod(String destroyMethod) {
        synchronized (this.postProcessingLock) {
            if (isExternallyManagedDestroyMethod(destroyMethod)) {
                return true;
            }
            if (this.externallyManagedDestroyMethods != null) {
                for (String candidate : this.externallyManagedDestroyMethods) {
                    int indexOfDot = candidate.lastIndexOf('.');
                    if (indexOfDot >= 0) {
                        String methodName = candidate.substring(indexOfDot + 1);
                        if (methodName.equals(destroyMethod)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
    }

    /**
     * Get all externally managed destruction methods (as an immutable Set).
     * <p>See {@link #registerExternallyManagedDestroyMethod} for details
     * regarding the format for the destruction methods in the returned set.
     *
     * @since 5.3.11
     */
    public Set<String> getExternallyManagedDestroyMethods() {
        synchronized (this.postProcessingLock) {
            return (this.externallyManagedDestroyMethods != null ?
                    Collections.unmodifiableSet(new LinkedHashSet<>(this.externallyManagedDestroyMethods)) :
                    Collections.emptySet());
        }
    }


    @Override
    public RootBeanDefinition cloneBeanDefinition() {
        return new RootBeanDefinition(this);
    }

    @Override
    public boolean equals(Object other) {
        return (this == other || (other instanceof RootBeanDefinition && super.equals(other)));
    }

    @Override
    public String toString() {
        return "Root bean: " + super.toString();
    }

}

